#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#ifdef _WIN32
#pragma warning (disable:6326)
#pragma warning (disable:6328)
#pragma warning (disable:4512)
#endif

#include <stdio.h>
#include <time.h>

#include <gtest4c.h>
#include <gutil.h>
#include <gconn.h>
#include <gmpi.h>

#include <gigitest.h>

extern struct gtest_case_def connPool_testCases[];
extern struct gtest_case_def service_testCases[];
extern struct gtest_case_def nodes_testCases[];

int main (int argc, char* argv[])
{
  int n;

  FD_SOCKET_STARTUP ();

  gtest_init(&argc, argv);

  gtest_register(nodes_testCases);
  gtest_register(connPool_testCases);
  gtest_register(service_testCases);

  n = gtest_run();

  return n;
}

#ifdef WINDOWS
unsigned long __stdcall CommTest_ThreadProc(void* p)
#else
void* CommTest_ThreadProc(void* p)
#endif
{
  int n;
  CommTest_Node* pCtx = (CommTest_Node*)p;

  pCtx->func(pCtx);

  if (pCtx->service)
  {
    gigi_close_service(pCtx->service);
  }

  if (pCtx->mpi_object)
  {
    for (n = 0; n< pCtx->mpi_object->_size; n++)
    {
      gigi_closesocket (pCtx->mpi_object->_nodes[n]._fd, 0 );
    }

    free (pCtx->mpi_object);
  }

  return 0;
};

int CommTest_Start (int size, CommTest_Node* ctx, GIGI_ID * nodes,
                    const char * conn_string, void(*func)(CommTest_Node*),
                    GIGI_ID service_id, int join)
{
  static unsigned short sid=123;
  char token_string[128];
  int i,nErr = 0, n;

  sprintf(token_string,"gigitest %d %d", (int)time(0), (int)rand());

  if (service_id == 0)
  {
    service_id = GIGI_ID_MAKE1(sid++);
  };

  memset(ctx,0, sizeof(CommTest_Node) * size );

  for ( i = 0; i < size; i++)
  {
    CommTest_Node* pComm = ctx + i;
    pComm->func =  func;
    pComm->id     = i;
    pComm->port   = TEST_PORT + i;
    pComm->count  = size;
    pComm->conn_string = conn_string ? conn_string : "";
    pComm->service_id = service_id;
    pComm->node_id = nodes;

    pComm->context  = gigi_create (pComm->conn_string, token_string, pComm->port, NULL);

    if (pComm->context == NULL)
    {
      nErr = 1;
      break;
    }

    MPIRegister (pComm->context);
    nodes[i] = gigi_my_id (pComm->context);

    pComm->service = gigi_open_service(pComm->context, pComm->service_id, 50);

    if ( pComm->service == NULL)
    {
      nErr = 2;
      break;
    }

    n = sizeof (struct gigi_mpi_context_t) + sizeof (gmpi_node) * pComm->count;

    pComm->mpi_object = (GIGI_MPI) malloc ( n ); 

    if (pComm->mpi_object == NULL)
    {
       nErr = 3;
       break;
    };

    memset (pComm->mpi_object, 0, n);

    pComm->mpi_object->_context = pComm->context;
    pComm->mpi_object->_id      = pComm->id;
    pComm->mpi_object->_service = pComm->service;
    pComm->mpi_object->_mpi_id  = pComm->service_id;
    pComm->mpi_object->_size    = pComm->count;
    pComm->mpi_object->_nodes   = (gmpi_node * ) ( pComm->mpi_object + 1);
  };

  if (nErr == 0)
  {
    for ( i = 0 ; i < size; i++)
    {
      int k;

      for (k = 0; k < size; k++)
      {
        ctx[i].mpi_object->_nodes[k]._node_id = nodes[k];
      };
    };

    for ( i = 0; i < size; i++)
    {
      unsigned long dwID;
      CommTest_Node* pComm = ctx + i;
      
      THREAD_BEGIN(pComm->thread,CommTest_ThreadProc,ctx + i,dwID);

      C_ASSERT_TRUE_EX( pComm->thread ,0 );

      if (pComm->thread == 0)
      {  
        break;
      }
    }
  }
  else
  {
    join = 0;
  };

  if (join)
  {
    CommTest_Join(ctx,size);
  }

  for(i = 0; i<size; i++)
  {
    CommTest_Node* pComm = ctx + i;

    if (pComm->context)
    {
      gigi_free (pComm->context);
      pComm->context = NULL;
    }
  }

  return size;
};


int CommTest_Join(CommTest_Node* ctx,int size)
{
  int i;
  for(i=0;i<size;i++)
  {
    if (ctx[i].thread == ((THREAD_T)0) ) continue;
    THREAD_JOIN(ctx[i].thread);
  }

  return size;
};

void CommTest_MPI(CommTest_Node* ctx,const char* pMPIName, int nRunCount)
{
  int i;
  GIGI_TIME StartTime = gigi_now();

  for(i = 0 ;i < nRunCount; i++)
  {
    if ( ctx->id == 0)
    {
      C_ASSERT_TRUE( GIGI_OK == gmpi_call(ctx->context,gigi_mpi_func_def(ctx->context, pMPIName),ctx->node_id,ctx->count,NULL, ctx->service_id,-1) );
    }
    else
    {
      C_ASSERT_TRUE( GIGI_OK == gmpi_service(ctx->context, NULL, ctx->service_id, 5000) );
    }

    if ( (i % 100) == 99 )
    {
      printf("%s at Node %d : %d finished\n", pMPIName, ctx->id, i + 1);
    };
  }

  if (ctx->id == 0)
  {
    printf("%s tps : %d\n",  pMPIName, gigi_calc_tps(StartTime,nRunCount * ctx->count) );
  }
};

void print_socket (const char * s, int id, GIGI_SOCKET fd)
{
  char buff[64],buff2[64];

  if (fd)
  {
    printf ("%s (%d) peer=%s service=%s create=%s fd=%d\n",
           s,
           id,
           node_id_to_string(fd->_node_pool->_node_id, buff),
           service_id_to_string(fd->_service_id, buff2),
           fd->_created ? "yes" : "no" ,
           fd->_tcp_fd._fd);
  }
  else
  {
    printf ("%s (%d) socket error\n", s, id);
  }
};

void print_ctx_info (CommTest_Node* ctx)
{
#ifdef GIGI_DEBUG
  GIGI_HANDLE context = ctx->context;
#endif

  printf ("node %d (%d)\n",
      ctx->id, 
      ctx->count
  );

#ifdef GIGI_DEBUG
  printf (
    "CacheHit     = %d\n"
    "CacheMissing = %d\n", 
    context->_cacheHit, 
    context->_cacheMissing
  );
#endif
};

GIGI_SOCKET create_channel (CommTest_Node * ctx, int from, int to)
{
  GIGI_SOCKET fd;

  if (from == ctx->id)
  {
    fd = gigi_socket (ctx->context, ctx->node_id[to], ctx->service_id, 0, NULL);
  }
  else if (to == ctx->id)
  {
    GIGI_ID FM;
    fd = gigi_accept (ctx->context, &FM, ctx->service_id,  SHAKEHAND_TIMEOUT);
  }
  else
  {
    fd = 0;
  }

  return fd;
};

int create_ring (CommTest_Node * ctx, GIGI_SOCKET * prevFD,
                 GIGI_SOCKET * nextFD)
{
  int id = ctx->id;
  int nextid = (id + 1) % ctx->count;
  int previd = (id + ctx->count - 1) % ctx->count;
  
  if (ctx->count == 1) return -1;
  
  if (id & 1)
  {
    *prevFD = create_channel (ctx, previd, id);
    *nextFD = create_channel (ctx, id, nextid);
  }
  else
  {
    *nextFD = create_channel (ctx, id, nextid);
    *prevFD = create_channel (ctx, previd, id);
  }

  return 0;
};

int close_ring (CommTest_Node* ctx,  GIGI_SOCKET rIN, GIGI_SOCKET rOUT)
{
  int nR1, nR2;
  if (ctx->id & 1)
  {
    nR1 = gigi_closesocket (rOUT, 0);
    nR2 = gigi_closesocket (rIN, 0);
  }
  else
  {
    nR1 = gigi_closesocket (rIN, 0);
    nR2 = gigi_closesocket (rOUT, 0);
  }

  return nR1 == 0 && nR2 == 0;
}

int sendrecv_ring (GIGI_SOCKET in, GIGI_SOCKET out,
                   int id,  int type, 
                   void* sendBuffer, void* recvBuffer, int size)
{
  int nRecvSize = size;

  if ( id & 1)
  {
    gigi_send_object (out, type, sendBuffer, size);
    gigi_recv_object (in, type, recvBuffer, &nRecvSize);
  }
  else
  {
    gigi_recv_object (in, type, recvBuffer, &nRecvSize);
    gigi_send_object (out, type, sendBuffer, size);
  }

  return nRecvSize;
};

int sendrecv (GIGI_SOCKET fd,  int type,  void* buffer, int* size,
              int myid, int from, int to)
{
  if (fd ==0)
  {
    return -1;
  }
  
  if (myid == from)
  {
    return gigi_send_object (fd, type, buffer, size ? *size : 0);
  }

  if (myid == to)
  {
    return gigi_recv_object (fd, type, buffer, size);
  }

  return -1;
};

int GenTempFile (const char* filename, int size)
{
  char buff[1024];
  int nRet = 0;
  GIGI_STREAM fp;
  
  fp = gigi_fopen (filename, "w");
  
  if (fp == NULL)
  {
    return -1;
  }

  memset (buff, 0, sizeof(buff));

  while (size > 0)
  {
    int nS = size <= sizeof (buff) ? size : sizeof (buff);

    if ( gigi_fwrite(fp, buff, nS) <= 0 )
    {
      nRet = -2;
      break;
    }

    size -= nS;
  }
  
  gigi_fclose (fp);

  return nRet;
}

void remove_file(const char* filename)
{
#ifdef WINDOWS
  DeleteFileA (filename);
#else
  unlink (filename);
#endif
};

char* GenTempFileName(char* filename, int size)
{
  static int id = 0;
  int filename_size;

#ifdef WINDOWS
  GetEnvironmentVariableA ("TEMP", filename, size );
        filename_size = (int)strlen (filename);
  if (filename[filename_size-1] != '\\')
  {
      strcat (filename + filename_size, "\\");
      filename_size++;
  }
#else
  strcpy (filename, "/tmp/");
  filename_size = (int)strlen (filename);
#endif
  
  sprintf ( filename + filename_size, 
    "test%d_%d",
    (int)time(0),
    id++
  );

  return filename;
}
